// ----------------
// Project:
// ESA Elevator
// ----------------
//
// Description:
// ----------------
// cabin_ctrl.v testbench
//
// Version History:
// ----------------
// 140203: removed #(2*CLK_PERIOD) in line 161
// 140131: added #(2*CLK_PERIOD) in line 236
// 140131: took back reduction (140116) to be more robust against little inaccuracies
// 140116: reduced for-loop-iteration by one in task motor_step

`timescale 1ns / 1ps


module cabin_ctrl_tb_public;

  //****************** SIMULATION PARAMETERS *******************//
  localparam CLK_PERIOD          =       50; // [ns] -> 20 MHz  
  localparam NUM_FLOORS          =       99; // 99 floors              
  localparam FLOOR_HEIGHT        =     3000; // [mm]                                                            
  localparam CTRL_STEP_CABIN     =       10; // [mm]       
  localparam DOOR_STOP_INTERVAL  =        1; // delay before door close [ms]      
  //***********************************************************//

  //****************** TESTBENCH PARAMETERS *******************//   
  localparam FLOOR_BITS              = min_1(ceil_log2(NUM_FLOORS-1)); 
  localparam DISTANCE_BUILDING       = ((NUM_FLOORS-1) * FLOOR_HEIGHT);
  localparam DISTANCE_BITS_BUILDING  = min_1(ceil_log2(DISTANCE_BUILDING));
  localparam DISTANCE_BITS_FLOORS    = min_1(ceil_log2(FLOOR_HEIGHT));
  localparam CONTROL_STEP_BITS_CABIN = min_1(ceil_log2(DISTANCE_BUILDING  / CTRL_STEP_CABIN));
  localparam WAIT_CYCLE              = (DOOR_STOP_INTERVAL * 1000000) / CLK_PERIOD;
  localparam WAIT_CYCLE_BITS         = min_1(ceil_log2(WAIT_CYCLE));
  localparam WAIT_INTERVAL           = (CLK_PERIOD*WAIT_CYCLE);
  //***********************************************************//  

  //********************* MODULE INPUTS ***********************//  
  reg                  CLK;
  reg                  RESET;
  reg                  MANUAL_DOOR_CLOSE;
  reg                  MANUAL_DOOR_OPEN;
  reg                  MANUAL_ALARM;
  reg                  OBJECT_DETECTED;
  reg                  SPEED_OK;
  reg                  WEIGHT_OK;
  reg                  SMOKE_DETECTED;
  reg [FLOOR_BITS-1:0] NEXT_FLOOR;
  reg                  DOOR_MOTOR_DONE;
  reg                  ELEVATOR_MOTOR_DONE;
  reg                  ELEVATOR_MOTOR_TICK;
  //***********************************************************// 


  //********************* MODULE OUTPUTS **********************//  
  wire [FLOOR_BITS-1:0]             CURRENT_FLOOR;
  wire                              HALTED;
  wire                              OPEN_DOOR;
  wire                              CLOSE_DOOR;
  wire                              ELEVATOR_UP;
  wire                              ELEVATOR_DOWN;
  wire [DISTANCE_BITS_BUILDING-1:0] DISTANCE;
  wire                              EMERGENCY_BRAKE;
  //***********************************************************// 
  

  //******************* UUT INSTANTIATION *********************// 
  cabin_ctrl  #(.FLOOR_BITS             (FLOOR_BITS),
                .CONTROL_STEP_DISTANCE  (CTRL_STEP_CABIN),
                .DISTANCE_BITS_BUILDING (DISTANCE_BITS_BUILDING),
                .DISTANCE_BITS_FLOORS   (DISTANCE_BITS_FLOORS),
                .FLOOR_HEIGHT           (FLOOR_HEIGHT),
                .WAIT_CYCLE             (WAIT_CYCLE),
                .WAIT_CYCLE_BITS        (WAIT_CYCLE_BITS))
     uut (
       .CLK                 (CLK), 
       .RESET               (RESET), 
       .MANUAL_DOOR_CLOSE   (MANUAL_DOOR_CLOSE), 
       .MANUAL_DOOR_OPEN   (MANUAL_DOOR_OPEN), 
       .MANUAL_ALARM        (MANUAL_ALARM), 
       .OBJECT_DETECTED     (OBJECT_DETECTED), 
       .SPEED_OK            (SPEED_OK), 
       .WEIGHT_OK           (WEIGHT_OK), 
       .SMOKE_DETECTED      (SMOKE_DETECTED), 
       .NEXT_FLOOR          (NEXT_FLOOR), 
       .DOOR_MOTOR_DONE     (DOOR_MOTOR_DONE), 
       .ELEVATOR_MOTOR_DONE (ELEVATOR_MOTOR_DONE), 
       .ELEVATOR_MOTOR_TICK (ELEVATOR_MOTOR_TICK), 
       .CURRENT_FLOOR       (CURRENT_FLOOR), 
       .HALTED              (HALTED), 
       .OPEN_DOOR           (OPEN_DOOR), 
       .CLOSE_DOOR          (CLOSE_DOOR), 
       .ELEVATOR_UP         (ELEVATOR_UP), 
       .ELEVATOR_DOWN       (ELEVATOR_DOWN), 
       .DISTANCE            (DISTANCE), 
       .EMERGENCY_BRAKE     (EMERGENCY_BRAKE));
  //***********************************************************// 


  //******************* TESTBENCH SIGNALS *********************//    
  time                                measure_timer;       // timer check
  reg                                 door_state;          // 1 (door open), 0 (door closed)
  reg                                 re_opening;
  wire [CONTROL_STEP_BITS_CABIN-1:0]  distance_ctrl_steps;
  //***********************************************************// 

  assign distance_ctrl_steps = DISTANCE / CTRL_STEP_CABIN;

  //******************* 20 MHz CLOCK SIGNAL *******************//   
  always begin 
    #(CLK_PERIOD/2) CLK = ~CLK;
  end
  //***********************************************************// 
  
  //********************* TEST INITIATION *********************//   
  initial begin
    // initalize simulation variables
    door_state          = 1; // door is open
    re_opening          = 0;
    measure_timer       = 0;
    //
    // Initialize Inputs
    CLK                 = 0;
    RESET               = 1;  
    MANUAL_DOOR_CLOSE   = 0;
    MANUAL_DOOR_OPEN   = 0;
    MANUAL_ALARM        = 0;
    OBJECT_DETECTED     = 1;
    SPEED_OK            = 1;
    WEIGHT_OK           = 1;
    SMOKE_DETECTED      = 0;
    NEXT_FLOOR          = 0;
    DOOR_MOTOR_DONE     = 0;
    ELEVATOR_MOTOR_DONE = 0;
    ELEVATOR_MOTOR_TICK = 0; 
    #(3*CLK_PERIOD/2);
    RESET               = 0;
    $display("************ STARTING SIMULATION ************"); 
    #(CLK_PERIOD);
    /*********************** REQUEST FIRST FLOOR ***********************/
    OBJECT_DETECTED     = 0;
    NEXT_FLOOR          = 1;
    
    // check delay before door close
    wait_close_door_cmd();
    
    // door closing now interrupted by object in the lightbarrier
    #(100*CLK_PERIOD);
    OBJECT_DETECTED     = 1;
    
    // check  immediate halt
    check_motor_stop();
    
    
    // door is free again after 5000 ns
    #(100*CLK_PERIOD);
    OBJECT_DETECTED     = 0;
    // delay before reopen door
    wait_reopen_door();
    
    // door re_opening
    #(100*CLK_PERIOD);
    DOOR_MOTOR_DONE     = 1;

    #(CLK_PERIOD);
    // check delay before door close - but manual close button pressed
    wait_manual_close_door_cmd();
      
    // close door
    #(100*CLK_PERIOD);
    DOOR_MOTOR_DONE     = 1;
    #(2*CLK_PERIOD);
/*=============================================================*/       
    // DRIVING TO 1ST FLOOR
    //Elevator should drive now
    motor_step(distance_ctrl_steps);
    // door should open right away
    wait_open_door();
    
    // open door
    #(100*CLK_PERIOD);
    ELEVATOR_MOTOR_DONE = 0;   
    DOOR_MOTOR_DONE     = 1;   
    #(2*CLK_PERIOD); 
/*=============================================================*/           
    //Head to the 4th floor  
    NEXT_FLOOR          = 4;

    // check delay before door close
    wait_close_door_cmd();
    
    // door closing now interrupted by object in the lightbarrier
    #(100*CLK_PERIOD);
    DOOR_MOTOR_DONE     = 1;
    #(2*CLK_PERIOD);
    
    //Elevator should drive now
    motor_step(distance_ctrl_steps);
    // door should open right away
    wait_open_door();
    
    // open door
    #(100*CLK_PERIOD);
    ELEVATOR_MOTOR_DONE = 0;     
    DOOR_MOTOR_DONE     = 1; 
    #(2*CLK_PERIOD); 
/*=============================================================*/           
    //Head to the 0th floor  
    NEXT_FLOOR          = 0;

    // check delay before door close
    wait_close_door_cmd();
    
    // door closing now interrupted by object in the lightbarrier
    #(100*CLK_PERIOD);
    ELEVATOR_MOTOR_DONE = 0; 
    DOOR_MOTOR_DONE     = 1;
    #(2*CLK_PERIOD);
    
    //Elevator should drive now but targe is updated just before FLOOR 2
    partial_motor_step(distance_ctrl_steps, 3);
    NEXT_FLOOR          = 1;
    continue_motor_step();
    motor_step(distance_ctrl_steps);
    
    // door should open right away
    wait_open_door();
    
    // open door
    #(100*CLK_PERIOD);
    ELEVATOR_MOTOR_DONE = 0; 
    DOOR_MOTOR_DONE     = 1; 
    #(2*CLK_PERIOD);	 

    //Head to the 10th floor
    NEXT_FLOOR = 10;

    // check delay before door close
    wait_close_door_cmd();
    
    // door closing now interrupted by object in the lightbarrier
    #(100*CLK_PERIOD);
    ELEVATOR_MOTOR_DONE = 0; 
    DOOR_MOTOR_DONE     = 1;
    #(2*CLK_PERIOD);
	 
    #(CLK_PERIOD);
    $display("************ SIMULATION COMPLETE ************");
    $finish;
  end


initial begin : kill
  integer i;
  for (i = 0; i < 10000; i=i+1)
  begin
	#(1000000*CLK_PERIOD);
  end
  $display("************ SIMULATION KILLED BECAUSE OF ERROR ************");
  $finish;
end


  //******************* GENERATE MOTOR TICKS ******************// 
   task motor_step;
     input [DISTANCE_BITS_BUILDING-1:0] distance_task;
     integer i;
     begin
     $display("Starting to issue motor step signals for %d mm, to next floor", distance_task * CTRL_STEP_CABIN);
       for(i=0;i<distance_task+1;i=i+1) begin
         #(2*CLK_PERIOD);
         ELEVATOR_MOTOR_TICK = 1;
         #(2*CLK_PERIOD);
         ELEVATOR_MOTOR_TICK = 0;
       end       
       ELEVATOR_MOTOR_DONE = 1;
    end
  endtask
  
   task partial_motor_step;
     input [DISTANCE_BITS_BUILDING-1:0] distance_task;
     input [31                      :0] part;
     integer i;
     begin
     $display("Starting to issue motor step signals for %d mm of %d mm", (distance_task/part)* CTRL_STEP_CABIN, distance_task* CTRL_STEP_CABIN);
       for(i=0;i<distance_task/part-1;i=i+1) begin
         #(2*CLK_PERIOD);
         ELEVATOR_MOTOR_TICK = 1;
         #(2*CLK_PERIOD);
         ELEVATOR_MOTOR_TICK = 0;
       end       
    end
  endtask
  //***********************************************************//   
  


  //****************** TO CHECK WAIT INTERVAL *****************// 
  task wait_close_door_cmd;
     begin       
      // wait WAIT_CYCLE cycles before door close
      $display("wait cycle before door close: %d cycles", WAIT_CYCLE);
      measure_timer    = $time; 
      while(CLOSE_DOOR == 0) begin
        #(CLK_PERIOD);
      end
      #(CLK_PERIOD);
      DOOR_MOTOR_DONE     = 0;
      if(($time-measure_timer) != 0)
        $display("delay of %d ns before closing the door", ($time-measure_timer));
    end
  endtask

  
  task wait_manual_close_door_cmd;
     begin       
      // wait WAIT_CYCLE cycles before door close
      $display("wait cycle before door close: %d cycles", WAIT_CYCLE);
      measure_timer    = $time;
      #(WAIT_INTERVAL/2);
      MANUAL_DOOR_CLOSE = 1;
      #(CLK_PERIOD);
      MANUAL_DOOR_CLOSE = 0;
      DOOR_MOTOR_DONE   = 0;
      if(($time-measure_timer) != 0)
        $display("delay of %d us before closing the door manually", ($time-measure_timer)/1000);
    end
  endtask   

  task wait_open_door;
     begin       
      // wait WAIT_CYCLE cycles before door open
      //$display("wait cycle before door open: %d cycles", WAIT_CYCLE);
      #(CLK_PERIOD);
      measure_timer    = $time; 
      while(OPEN_DOOR == 0) begin
        #(CLK_PERIOD);
      end
      DOOR_MOTOR_DONE     = 0;
      if(($time-measure_timer) > 50)
        $display("unexpected delay of %d us before opening the door", ($time-measure_timer)/1000);
    end
  endtask
  
  task wait_reopen_door;
     begin
      re_opening = 1;       
      // wait WAIT_CYCLE cycles before door reopen
      $display("wait cycle before door reopen: %d cycles", WAIT_CYCLE);
      measure_timer    = $time; 
      while(OPEN_DOOR == 0) begin
        #(CLK_PERIOD);
      end
      DOOR_MOTOR_DONE     = 0;   
      $display("door motor stopped for %d us, reopening door", ($time-measure_timer)/1000);
      re_opening = 0;       
    end
  endtask
  //***********************************************************//
  
  task wait_door_motor_done;
     begin
      measure_timer    = $time; 
      while(DOOR_MOTOR_DONE == 0) begin
        #(CLK_PERIOD);
      end 
    end
  endtask
  
  task check_motor_stop;
    begin
      #(CLK_PERIOD);
      if ((OPEN_DOOR & CLOSE_DOOR)== 0) begin
        $display("the door motor should have stopped here");
      end
      else begin
        $display("door motor stopped immediately");
      end
    end
  endtask
  

  task continue_motor_step;
     begin       
      while(ELEVATOR_DOWN == 0 && ELEVATOR_UP == 0) begin
        #(2*CLK_PERIOD);
        ELEVATOR_MOTOR_TICK = 1;
        if (ELEVATOR_DOWN == 0 && ELEVATOR_UP == 0) begin
          #(2*CLK_PERIOD);
          ELEVATOR_MOTOR_TICK = 0;
        end
      end
    end
  endtask


  //****************** VISUALISATION PROCESS ******************//
  always@(posedge CLOSE_DOOR) begin
    if(OPEN_DOOR & CLOSE_DOOR)
    $display("Stopping door movement");    
    else
    $display("closing door now");
  end
  
  always@(posedge OPEN_DOOR) begin
    if(OPEN_DOOR & CLOSE_DOOR) begin
    end
    else if (~re_opening)
    $display("Opening door now");    
  end
   
  always@(posedge DOOR_MOTOR_DONE) begin
    if(door_state) begin
      $display("Finished door movement - door is open");
    end
    else begin
      $display("Finished door movement - door is closed");
    end
    door_state = ~door_state;
  end
  
  always@(posedge OBJECT_DETECTED) begin
    if (~RESET)
    $display("Object detected");
  end
  
  always@(negedge OBJECT_DETECTED) begin
    if (~RESET)
    $display("Door is free of objects");
  end
  
  always@(NEXT_FLOOR) begin
    if (~RESET)
    $display("Received request to go to floor %d @ floor %d", NEXT_FLOOR, CURRENT_FLOOR);  
  end
  
  always@(posedge ELEVATOR_UP) begin
    $display("Driving UP %dmm to floor %d",DISTANCE, NEXT_FLOOR);
  end
  
  always@(posedge ELEVATOR_DOWN) begin
    $display("Driving DOWN %dmm to floor %d", DISTANCE, NEXT_FLOOR);
  end
  
  always@(posedge HALTED) begin
    if (~RESET)
    $display("cabin stopped at floor %d", CURRENT_FLOOR);
    else
    $display("initial cabin position at floor %d", CURRENT_FLOOR);
  end
  //***********************************************************//  
      
      
  //*********************    FUNCTIONS     *********************// 
  //ceil of the log base 2
  function integer ceil_log2;
    input [31:0] value;
    for (ceil_log2=0; value>0; ceil_log2=ceil_log2+1)
      value = value>>1;
  endfunction
  
  // value cannot be less than 1
  function integer min_1;
    input [31:0] value;
    if (value == 0)
      min_1 = 1;
    else
      min_1 = value;
  endfunction  
  //************************************************************// 
        
endmodule
